/* OggEnc
 *
 * This program is distributed under the GNU General Public License, version 2.
 * A copy of this license is included with this source.
 *
 * Copyright 2000-2002, Michael Smith <msmith@xiph.org>
 *
 * Portions from Vorbize, (c) Kenneth Arnold <kcarnold@yahoo.com>
 * and libvorbis examples, (c) Monty <monty@xiph.org>
 */

using System;
using System.IO;
using System.Text;

using Gnu.Getopt;

using Xiph.Interop.Vorbis;

namespace Xiph.VorbisTools.OggEnc
{
    class Oggenc
    {
        private const string VERSION_STRING = "OggEnc v1.0.1 (libvorbis 1.0.1)\n";
        private const string COPYRIGHT = "(c) 2000-2003 Michael Smith <msmith@xiph.org>\n";
        private const int CHUNK = 4096; /* We do reads, etc. in multiples of this */
        private static LongOpt[] long_options = new LongOpt[] 
		{
			new LongOpt("quiet",Argument.No,null,'Q'),
			new LongOpt("help",Argument.No,null,'h'),
			new LongOpt("comment",Argument.Required,null,'c'),
			new LongOpt("artist",Argument.Required,null,'a'),
			new LongOpt("album",Argument.Required,null,'l'),
			new LongOpt("title",Argument.Required,null,'t'),
			new LongOpt("genre",Argument.Required,null,'G'),
			new LongOpt("names",Argument.Required,null,'n'),
			new LongOpt("name-remove",Argument.Required,null,'X'),
			new LongOpt("name-replace",Argument.Required,null,'P'),
			new LongOpt("output",Argument.Required,null,'o'),
			new LongOpt("version",Argument.No,null,'v'),
			new LongOpt("raw",Argument.No,null,'r'),
			new LongOpt("raw-bits",Argument.Required,null,'B'),
			new LongOpt("raw-chan",Argument.Required,null,'C'),
			new LongOpt("raw-rate",Argument.Required,null,'R'),
			new LongOpt("raw-endianness",Argument.Required,null, 0),
			new LongOpt("bitrate",Argument.Required,null,'b'),
			new LongOpt("min-bitrate",Argument.Required,null,'m'),
			new LongOpt("max-bitrate",Argument.Required,null,'M'),
			new LongOpt("quality",Argument.Required,null,'q'),
			new LongOpt("date",Argument.Required,null,'d'),
			new LongOpt("tracknum",Argument.Required,null,'N'),
			new LongOpt("serial",Argument.Required,null,'s'),
			new LongOpt("managed", Argument.No, null, 0),
			new LongOpt("resample",Argument.Required,null,0),
			new LongOpt("downmix", Argument.No,null,0),
			new LongOpt("scale", Argument.Required, null, 0), 
			new LongOpt("advanced-encode-option", Argument.Required, null, 0),
			new LongOpt("discard-comments", Argument.No, null, 0)
		};


        [STAThread]
        static int Main(string[] args)
        {
            /* Default values */
            oe_options opt = new oe_options(null, 0, null, 0, null, 0, null, 0, null,
                0, null, 0, null, 0, null, 0, 1, false, false, 16, 44100, 2, 0, null,
                Platform.DEFAULT_NAMEFMT_REMOVE, Platform.DEFAULT_NAMEFMT_REPLACE,
                null, false, -1, -1, -1, .3f, -1, 0, false, 0.0f, 0);

            int i;

            string[] infiles;
            int numfiles;
            int errors = 0;

            //			setlocale(LC_ALL, "");
            //			bindtextdomain(PACKAGE, LOCALEDIR);
            //			textdomain(PACKAGE);

            Getopt g = new Getopt("oggenc", args, "A:a:b:B:c:C:d:G:hl:m:M:n:N:o:P:q:QrR:s:t:vX:", long_options);
            g.Opterr = false; // We'll do our own error handling

            parse_options(args, opt, g);

            if (g.Optind >= args.Length)
            {
                Console.Error.WriteLine("{0}{1}", VERSION_STRING, COPYRIGHT);
                Console.Error.WriteLine("ERROR: No input files specified. Use -h for help.");
                return 1;
            }
            else
            {
                numfiles = args.Length - g.Optind;
                infiles = new string[numfiles];
                Array.Copy(args, g.Optind, infiles, 0, numfiles);
            }

            /* Now, do some checking for illegal argument combinations */

            for (i = 0; i < numfiles; i++)
            {
                if (infiles[i].Equals("-") && numfiles > 1)
                {
                    Console.Error.WriteLine("ERROR: Multiple files specified when using stdin");
                    Environment.Exit(1);
                }
            }

            if (numfiles > 1 && opt.outfile != null)
            {
                Console.Error.WriteLine("ERROR: Multiple input files with specified output filename: suggest using -n");
                Environment.Exit(1);
            }

            if (opt.serial == 0)
            {
                /* We randomly pick a serial number. This is then incremented for each file */
                Random r = new Random();
                opt.serial = (uint)r.Next();
            }

            for (i = 0; i < numfiles; i++)
            {
                /* Once through the loop for each file */

                oe_enc_opt enc_opts = new oe_enc_opt();
                VorbisComment vc = new VorbisComment();
                string out_fn = null;
                Stream sin, sout = null;
                bool foundformat = false;
                bool closeout = false, closein = false;
                string artist = null, album = null, title = null, track = null;
                string date = null, genre = null;
                input_format format = new input_format();

                /* Set various encoding defaults */

                enc_opts.serialno = opt.serial++;
                enc_opts.progress_update = new progress_func(Encode.update_statistics_full);
                enc_opts.start_encode = new enc_start_func(Encode.start_encode_full);
                enc_opts.end_encode = new enc_end_func(Encode.final_statistics);
                enc_opts.error = new error_func(Encode.encode_error);
                enc_opts.comments = vc;
                enc_opts.copy_comments = opt.copy_comments;

                /* OK, let's build the vorbis_comments structure */
                build_comments(vc, opt, i, ref artist, ref album, ref title, ref track,
                    ref date, ref genre);

                if (infiles[i].Equals("-"))
                {
                    Console.SetIn(new StreamReader(Console.OpenStandardInput()));
                    sin = Console.OpenStandardInput();
                    infiles[i] = null;
                    if (opt.outfile == null)
                    {
                        Console.SetOut(new StreamWriter(Console.OpenStandardOutput()));
                        sout = Console.OpenStandardOutput();
                    }
                }
                else
                {
                    try
                    {
                        sin = File.Open(infiles[i], FileMode.Open, FileAccess.Read);
                    }
                    catch (Exception err)
                    {
                        Console.Error.WriteLine("ERROR: Cannot open input file \"{0}\": {1}", infiles[i], err.Message);
                        out_fn = null;
                        errors++;
                        continue;
                    }

                    closein = true;
                }

                /* Now, we need to select an input audio format - we do this before opening
                   the output file so that we don't end up with a 0-byte file if the input
                   file can't be read */

                if (opt.rawmode)
                {
                    enc_opts.rate = opt.raw_samplerate;
                    enc_opts.channels = opt.raw_channels;
                    enc_opts.samplesize = opt.raw_samplesize;
                    enc_opts.endianness = opt.raw_endianness != 0;
                    Audio.raw_open(sin, enc_opts);
                    foundformat = true;
                }
                else
                {
                    format = Audio.open_audio_file(sin, enc_opts);
                    if (format != null)
                    {
                        if (!opt.quiet)
                            Console.Error.WriteLine("Opening with {0} module: {1}",
                                format.format, format.description);
                        foundformat = true;
                    }

                }

                if (!foundformat)
                {
                    Console.Error.WriteLine("ERROR: Input file \"{0}\" is not a supported format", (infiles[i] != null) ? infiles[i] : "(stdin)");
                    if (closein)
                        sin.Close();
                    errors++;
                    continue;
                }

                /* Ok. We can read the file - so now open the output file */

                if (opt.outfile != null && opt.outfile.Equals("-"))
                {
                    Console.SetOut(new StreamWriter(Console.OpenStandardOutput()));
                    sout = Console.OpenStandardOutput();
                }
                else if (sout == null)
                {
                    if (opt.outfile != null)
                    {
                        out_fn = string.Copy(opt.outfile);
                    }
                    else if (opt.namefmt != null)
                    {
                        out_fn = generate_name_string(opt.namefmt, opt.namefmt_remove,
                            opt.namefmt_replace, artist, title, album, track, date,
                            genre);
                    }
                    /* This bit was widely derided in mid-2002, so it's been removed */
                    /*
                    else if(opt.title)
                    {
                        out_fn = malloc(strlen(title) + 5);
                        strcpy(out_fn, title);
                        strcat(out_fn, ".ogg");
                    }
                    */
                    else if (infiles[i] != null)
                    {
                        /* Create a filename from existing filename, replacing extension with .ogg */
                        string start;
                        int end;

                        start = infiles[i];
                        end = infiles[i].LastIndexOf('.');
                        end = (end >= 0) ? end : (infiles[i].Length + 1);

                        out_fn = start.Substring(0, end);
                        out_fn += ".ogg";
                    }
                    else
                    {
                        Console.Error.WriteLine("WARNING: No filename, defaulting to \"default.ogg\"");
                        out_fn = string.Copy("default.ogg");
                    }

                    /* Create any missing subdirectories, if possible */
                    if (Platform.create_directories(out_fn))
                    {
                        if (closein)
                            sin.Close();
                        Console.Error.WriteLine("ERROR: Could not create required subdirectories for output filename \"{0}\"", out_fn);
                        errors++;
                        out_fn = null;
                        continue;
                    }

                    try
                    {
                        sout = File.Open(out_fn, FileMode.OpenOrCreate, FileAccess.Write);	// OpenOrCreate
                    }
                    catch (Exception err)
                    {
                        if (closein)
                            sin.Close();
                        Console.Error.WriteLine("ERROR: Cannot open output file \"{0}\": {1}", out_fn, err.Message);
                        errors++;
                        out_fn = null;
                        continue;
                    }
                    closeout = true;
                }

                /* Now, set the rest of the options */
                enc_opts.sout = sout;
                enc_opts.comments = vc;
                enc_opts.filename = out_fn;
                enc_opts.infilename = infiles[i];
                enc_opts.managed = opt.managed;
                enc_opts.bitrate = opt.nominal_bitrate;
                enc_opts.min_bitrate = opt.min_bitrate;
                enc_opts.max_bitrate = opt.max_bitrate;
                enc_opts.quality = opt.quality;
                enc_opts.quality_set = opt.quality_set;
                enc_opts.advopt = opt.advopt;
                enc_opts.advopt_count = opt.advopt_count;

                if (opt.resamplefreq != 0 && opt.resamplefreq != enc_opts.rate)
                {
                    int fromrate = enc_opts.rate;
                    enc_opts.resamplefreq = opt.resamplefreq;
                    if (Audio.setup_resample(enc_opts))
                    {
                        errors++;
                        goto clear_all;
                    }
                    else if (!opt.quiet)
                        Console.Error.WriteLine("Resampling input from {0} Hz to {1} Hz", fromrate, opt.resamplefreq);
                }

                if (opt.downmix)
                {
                    if (enc_opts.channels == 2)
                    {
                        Audio.setup_downmix(enc_opts);
                        if (!opt.quiet)
                            Console.Error.WriteLine("Downmixing stereo to mono");
                    }
                    else
                    {
                        Console.Error.WriteLine("ERROR: Can't downmix except from stereo to mono");
                        errors++;
                        if (opt.resamplefreq != 0 && opt.resamplefreq != enc_opts.rate)
                            Audio.clear_resample(enc_opts);
                        goto clear_all;
                    }
                }

                if (opt.scale > 0.0f)
                {
                    Audio.setup_scaler(enc_opts, opt.scale);
                    if (!opt.quiet)
                        Console.Error.WriteLine("Scaling input to {0}", opt.scale);
                }


                if (enc_opts.total_samples_per_channel == 0)
                    enc_opts.progress_update = new progress_func(Encode.update_statistics_notime);

                if (opt.quiet)
                {
                    enc_opts.start_encode = new enc_start_func(Encode.start_encode_null);
                    enc_opts.progress_update = new progress_func(Encode.update_statistics_null);
                    enc_opts.end_encode = new enc_end_func(Encode.final_statistics_null);
                }

                if (Encode.oe_encode(enc_opts) != 0)
                    errors++;

                if (opt.scale > 0)
                    Audio.clear_scaler(enc_opts);
                if (opt.downmix)
                    Audio.clear_downmix(enc_opts);
                if (opt.resamplefreq != 0 && opt.resamplefreq != enc_opts.rate)
                    Audio.clear_resample(enc_opts);
            clear_all:

                if (out_fn != null)
                    out_fn = null;
                if (opt.outfile != null)
                    opt.outfile = null;
                VorbisComment.Clear(vc);
                if (!opt.rawmode)
                    format.close_func(enc_opts.readdata);

                if (closein)
                    sin.Close();
                if (closeout)
                    sout.Close();
            }/* Finished this file, loop around to next... */

            return (errors != 0) ? 1 : 0;
        }

        private static void usage()
        {
            Console.WriteLine("{0}{1}", VERSION_STRING, COPYRIGHT);
            Console.WriteLine("Usage: oggenc [options] input.wav [...]");
            Console.WriteLine("");
            Console.WriteLine("OPTIONS:");
            Console.WriteLine(" General:");
            Console.WriteLine(" -Q, --quiet          Produce no output to stderr");
            Console.WriteLine(" -h, --help           Print this help text");
            Console.WriteLine(" -r, --raw            Raw mode. Input files are read directly as PCM data");
            Console.WriteLine(" -B, --raw-bits=n     Set bits/sample for raw input. Default is 16");
            Console.WriteLine(" -C, --raw-chan=n     Set number of channels for raw input. Default is 2");
            Console.WriteLine(" -R, --raw-rate=n     Set samples/sec for raw input. Default is 44100");
            Console.WriteLine(" --raw-endianness     1 for bigendian, 0 for little (defaults to 0)");
            Console.WriteLine(" -b, --bitrate        Choose a nominal bitrate to encode at. Attempt");
            Console.WriteLine("                      to encode at a bitrate averaging this. Takes an");
            Console.WriteLine("                      argument in kbps. This uses the bitrate management");
            Console.WriteLine("                      engine, and is not recommended for most users.");
            Console.WriteLine("                      See -q, --quality for a better alternative.");
            Console.WriteLine(" -m, --min-bitrate    Specify a minimum bitrate (in kbps). Useful for");
            Console.WriteLine("                      encoding for a fixed-size channel.");
            Console.WriteLine(" -M, --max-bitrate    Specify a maximum bitrate in kbps. Useful for");
            Console.WriteLine("                      streaming applications.");
            Console.WriteLine(" -q, --quality        Specify quality between 0 (low) and 10 (high),");
            Console.WriteLine("                      instead of specifying a particular bitrate.");
            Console.WriteLine("                      This is the normal mode of operation.");
            Console.WriteLine("                      Fractional qualities (e.g. 2.75) are permitted");
            Console.WriteLine("                      Quality -1 is also possible, but may not be of");
            Console.WriteLine("                      acceptable quality.");
            Console.WriteLine(" --resample n         Resample input data to sampling rate n (Hz)");
            Console.WriteLine(" --downmix            Downmix stereo to mono. Only allowed on stereo");
            Console.WriteLine("                      input.");
            Console.WriteLine(" -s, --serial         Specify a serial number for the stream. If encoding");
            Console.WriteLine("                      multiple files, this will be incremented for each");
            Console.WriteLine("                      stream after the first.");
            Console.WriteLine(" --discard-comments   Prevents comments in FLAC and Ogg FLAC files from");
            Console.WriteLine("                      being copied to the output Ogg Vorbis file.");
            Console.WriteLine("");
            Console.WriteLine(" Naming:");
            Console.WriteLine(" -o, --output=fn      Write file to fn (only valid in single-file mode)");
            Console.WriteLine(" -n, --names=string   Produce filenames as this string, with %%a, %%t, %%l,");
            Console.WriteLine("                      %%n, %%d replaced by artist, title, album, track number,");
            Console.WriteLine("                      and date, respectively (see below for specifying these).");
            Console.WriteLine("                      %%%% gives a literal %%.");
            Console.WriteLine(" -X, --name-remove=s  Remove the specified characters from parameters to the");
            Console.WriteLine("                      -n format string. Useful to ensure legal filenames.");
            Console.WriteLine(" -P, --name-replace=s Replace characters removed by --name-remove with the");
            Console.WriteLine("                      characters specified. If this string is shorter than the");
            Console.WriteLine("                      --name-remove list or is not specified, the extra");
            Console.WriteLine("                      characters are just removed.");
            Console.WriteLine("                      Default settings for the above two arguments are platform");
            Console.WriteLine("                      specific.");
            Console.WriteLine(" -c, --comment=c      Add the given string as an extra comment. This may be");
            Console.WriteLine("                      used multiple times. The argument should be in the");
            Console.WriteLine("                      format \"tag=value\".");
            Console.WriteLine(" -d, --date           Date for track (usually date of performance)");
            Console.WriteLine(" -N, --tracknum       Track number for this track");
            Console.WriteLine(" -t, --title          Title for this track");
            Console.WriteLine(" -l, --album          Name of album");
            Console.WriteLine(" -a, --artist         Name of artist");
            Console.WriteLine(" -G, --genre          Genre of track");
            Console.WriteLine("                      If multiple input files are given, then multiple");
            Console.WriteLine("                      instances of the previous five arguments will be used,");
            Console.WriteLine("                      in the order they are given. If fewer titles are");
            Console.WriteLine("                      specified than files, OggEnc will print a warning, and");
            Console.WriteLine("                      reuse the final one for the remaining files. If fewer");
            Console.WriteLine("                      track numbers are given, the remaining files will be");
            Console.WriteLine("                      unnumbered. For the others, the final tag will be reused");
            Console.WriteLine("                      for all others without warning (so you can specify a date");
            Console.WriteLine("                      once, for example, and have it used for all the files)");
            Console.WriteLine("");
            Console.WriteLine("INPUT FILES:");
            Console.WriteLine(" OggEnc input files must currently be 24, 16, or 8 bit PCM WAV, AIFF, or AIFF/C");
            Console.WriteLine(" files, 32 bit IEEE floating point WAV, and optionally FLAC or Ogg FLAC. Files");
            Console.WriteLine("  may be mono or stereo (or more channels) and any sample rate.");
            Console.WriteLine(" Alternatively, the --raw option may be used to use a raw PCM data file, which");
            Console.WriteLine(" must be 16 bit stereo little-endian PCM ('headerless wav'), unless additional");
            Console.WriteLine(" parameters for raw mode are specified.");
            Console.WriteLine(" You can specify taking the file from stdin by using - as the input filename.");
            Console.WriteLine(" In this mode, output is to stdout unless an output filename is specified");
            Console.WriteLine(" with -o");
            Console.WriteLine("");
        }

        private static int strncpy_filtered(char[] dst, int dstIndex, string src, int len, string remove_list,
            string replace_list)
        {
            int hit, drop_margin;
            int used = 0;
            int remove_listIndex = 0;
            int srcIndex = 0;

            if (remove_list == null || remove_list.Length == 0)
            {
                src.CopyTo(0, dst, dstIndex, len - 1);
                dst[dstIndex + len - 1] = '\0';
                return new string(dst).Length;
            }

            drop_margin = remove_listIndex + (replace_list == null ? 0 : replace_list.Length);

            while ((src.Length - srcIndex) != 0 && used < len - 1)
            {
                if ((hit = remove_list.IndexOf(src[srcIndex])) >= 0)
                {
                    if (hit < drop_margin)
                    {
                        dst[dstIndex++] = replace_list[hit - remove_listIndex];
                        used++;
                    }
                }
                else
                {
                    dst[dstIndex++] = src[srcIndex];
                    used++;
                }
                srcIndex++;
            }
            dst[dstIndex] = '\0';

            return used;
        }

        private static string generate_name_string(string format, string remove_list,
            string replace_list, string artist, string title, string album,
            string track, string date, string genre)
        {
            //			StringBuilder buffer = new StringBuilder(CHUNK);
            char[] buffer = new char[CHUNK + 1];
            char next;
            string str;
            int used = 0;
            int buflen;

            buflen = CHUNK;
            int formatIndex = 0;

            while ((format.Length - formatIndex) != 0 && used < buflen)
            {
                next = format[formatIndex++];

                if (next == '%')
                {
                    switch (format[formatIndex++])
                    {
                        case '%':
                            buffer[used++] = '%';
                            break;
                        case 'a':
                            str = (artist != null) ? artist : "(none)";
                            used += strncpy_filtered(buffer, used, str, buflen - used,
                                remove_list, replace_list);
                            break;
                        case 'd':
                            str = (date != null) ? date : "(none)";
                            used += strncpy_filtered(buffer, used, str, buflen - used,
                                remove_list, replace_list);
                            break;
                        case 'g':
                            str = (genre != null) ? genre : "(none)";
                            used += strncpy_filtered(buffer, used, str, buflen - used,
                                remove_list, replace_list);
                            break;
                        case 't':
                            str = (title != null) ? title : "(none)";
                            used += strncpy_filtered(buffer, used, str, buflen - used,
                                remove_list, replace_list);
                            break;
                        case 'l':
                            str = (album != null) ? album : "(none)";
                            used += strncpy_filtered(buffer, used, str, buflen - used,
                                remove_list, replace_list);
                            break;
                        case 'n':
                            str = (track != null) ? track : "(none)";
                            used += strncpy_filtered(buffer, used, str, buflen - used,
                                remove_list, replace_list);
                            break;
                        default:
                            Console.Error.WriteLine("WARNING: Ignoring illegal escape character '{0}' in name format", format[formatIndex - 1]);
                            break;
                    }
                }
                else
                    buffer[(used++)] = next;
            }

            return buffer.ToString();
        }

        private static void parse_options(string[] args, oe_options opt, Getopt g)
        {
            int ret;
            //			int option_index = 1;

            while ((ret = g.getopt()) != -1)
            {
                switch (ret)
                {
                    case 0:
                        if (long_options[g.Optind].Name.Equals("managed"))
                        {
                            if (!opt.managed)
                            {
                                if (!opt.quiet)
                                    Console.Error.WriteLine("Enabling bitrate management engine");
                                opt.managed = true;
                            }
                        }
                        else if (long_options[g.Optind].Name.Equals("raw-endianness"))
                        {
                            if (!opt.rawmode)
                            {
                                opt.rawmode = true;
                                Console.Error.WriteLine("WARNING: Raw endianness specified for non-raw data. Assuming input is raw.");
                            }
                            try
                            {
                                opt.raw_endianness = int.Parse(g.Optarg);
                            }
                            catch (Exception)
                            {
                                Console.Error.WriteLine("WARNING: Couldn't read endianness argument \"{0}\"", g.Optarg);
                                opt.raw_endianness = 0;
                            }
                        }
                        else if (long_options[g.Optind].Name.Equals("resample"))
                        {
                            try
                            {
                                opt.resamplefreq = int.Parse(g.Optarg);
                            }
                            catch (Exception)
                            {
                                Console.Error.WriteLine("WARNING: Couldn't read resampling frequency \"{0}\"", g.Optarg);
                                opt.resamplefreq = 0;
                            }
                            if (opt.resamplefreq < 100) /* User probably specified it
                                                   in kHz accidently */
                                Console.Error.WriteLine("Warning: Resample rate specified as {0} Hz. Did you mean {1} Hz?",
                                    opt.resamplefreq, opt.resamplefreq * 1000);
                        }
                        else if (long_options[g.Optind].Name.Equals("downmix"))
                        {
                            opt.downmix = true;
                        }
                        else if (long_options[g.Optind].Name.Equals("scale"))
                        {
                            try
                            {
                                opt.scale = float.Parse(g.Optarg);
                            }
                            catch (Exception)
                            {
                                opt.scale = 0;
                                Console.Error.WriteLine("Warning: Couldn't parse scaling factor \"{0}\"",
                                    g.Optarg);
                            }
                        }
                        else if (long_options[g.Optind].Name.Equals("advanced-encode-option"))
                        {
                            string arg = string.Copy(g.Optarg);
                            int val;

                            val = arg.IndexOf('=');
                            if (val < 0)
                            {
                                Console.Error.WriteLine("No value for advanced encoder option found");
                                continue;
                            }
                            //							else
                            //								*val++=0;

                            opt.advopt = new adv_opt[(++opt.advopt_count)];
                            opt.advopt[opt.advopt_count - 1].arg = arg;
                            opt.advopt[opt.advopt_count - 1].val = arg.Substring(val + 1);
                        }
                        else if (long_options[g.Optind].Name.Equals("discard-comments"))
                        {
                            opt.copy_comments = 0;
                        }

                        else
                        {
                            Console.Error.WriteLine("Internal error parsing command line options");
                            Environment.Exit(1);
                        }

                        break;
                    case 'a':
                        opt.artist = new string[(++opt.artist_count)];
                        opt.artist[opt.artist_count - 1] = string.Copy(g.Optarg);
                        break;
                    case 'c':
                        if (g.Optarg.IndexOf('=') < 0)
                        {
                            Console.Error.WriteLine("Warning: Illegal comment used (\"{0}\"), ignoring.", g.Optarg);
                            break;
                        }
                        opt.comments = new string[(++opt.comment_count)];
                        opt.comments[opt.comment_count - 1] = string.Copy(g.Optarg);
                        break;
                    case 'd':
                        opt.dates = new string[(++opt.date_count)];
                        opt.dates[opt.date_count - 1] = string.Copy(g.Optarg);
                        break;
                    case 'G':
                        opt.genre = new string[(++opt.genre_count)];
                        opt.genre[opt.genre_count - 1] = string.Copy(g.Optarg);
                        break;
                    case 'h':
                        usage();
                        Environment.Exit(0);
                        break;
                    case 'l':
                        opt.album = new string[(++opt.album_count)];
                        opt.album[opt.album_count - 1] = string.Copy(g.Optarg);
                        break;
                    case 's':
                        /* Would just use atoi(), but that doesn't deal with unsigned
                         * ints. Damn */
                        try
                        {
                            opt.serial = uint.Parse(g.Optarg);
                        }
                        catch (Exception)
                        {
                            opt.serial = 0; /* Failed, so just set to zero */
                        }
                        break;
                    case 't':
                        opt.title = new string[(++opt.title_count)];
                        opt.title[opt.title_count - 1] = string.Copy(g.Optarg);
                        break;
                    case 'b':
                        try
                        {
                            opt.nominal_bitrate = int.Parse(g.Optarg);
                        }
                        catch (Exception)
                        {
                            Console.Error.WriteLine("Warning: nominal bitrate \"{0}\" not recognised", g.Optarg);
                            opt.nominal_bitrate = -1;
                        }

                        break;
                    case 'm':
                        try
                        {
                            opt.min_bitrate = int.Parse(g.Optarg);
                        }
                        catch (Exception)
                        {
                            Console.Error.WriteLine("Warning: minimum bitrate \"{0}\" not recognised", g.Optarg);
                            opt.min_bitrate = -1;
                        }
                        if (!opt.managed)
                        {
                            if (!opt.quiet)
                                Console.Error.WriteLine("Enabling bitrate management engine");
                            opt.managed = true;
                        }
                        break;
                    case 'M':
                        try
                        {
                            opt.max_bitrate = int.Parse(g.Optarg);
                        }
                        catch (Exception)
                        {
                            Console.Error.WriteLine("Warning: maximum bitrate \"{0}\" not recognised", g.Optarg);
                            opt.max_bitrate = -1;
                        }
                        if (!opt.managed)
                        {
                            if (!opt.quiet)
                                Console.Error.WriteLine("Enabling bitrate management engine");
                            opt.managed = true;
                        }
                        break;
                    case 'q':
                        try
                        {
                            opt.quality = float.Parse(g.Optarg);
                        }
                        catch (Exception)
                        {
                            Console.Error.WriteLine("Quality option \"{0}\" not recognised, ignoring", g.Optarg);
                            break;
                        }
                        opt.quality_set = 1;
                        opt.quality *= 0.1f;
                        if (opt.quality > 1.0f)
                        {
                            opt.quality = 1.0f;
                            Console.Error.WriteLine("WARNING: quality setting too high, setting to maximum quality.");
                        }
                        break;
                    case 'n':
                        if (opt.namefmt != null)
                        {
                            Console.Error.WriteLine("WARNING: Multiple name formats specified, using final");
                            opt.namefmt = null;
                        }
                        opt.namefmt = string.Copy(g.Optarg);
                        break;
                    case 'X':
                        if (opt.namefmt_remove != null && opt.namefmt_remove !=
                            Platform.DEFAULT_NAMEFMT_REMOVE)
                        {
                            Console.Error.WriteLine("WARNING: Multiple name format filters specified, using final");
                            opt.namefmt_remove = null;
                        }
                        opt.namefmt_remove = string.Copy(g.Optarg);
                        break;
                    case 'P':
                        if (opt.namefmt_replace != null && opt.namefmt_replace !=
                            Platform.DEFAULT_NAMEFMT_REPLACE)
                        {
                            Console.Error.WriteLine("WARNING: Multiple name format filter replacements specified, using final");
                            opt.namefmt_replace = null;
                        }
                        opt.namefmt_replace = string.Copy(g.Optarg);
                        break;
                    case 'o':
                        if (opt.outfile != null)
                        {
                            Console.Error.WriteLine("WARNING: Multiple output files specified, suggest using -n");
                            opt.outfile = null;
                        }
                        opt.outfile = string.Copy(g.Optarg);
                        break;
                    case 'Q':
                        opt.quiet = true;
                        break;
                    case 'r':
                        opt.rawmode = true;
                        break;
                    case 'v':
                        Console.WriteLine(VERSION_STRING);
                        Environment.Exit(0);
                        break;
                    case 'B':
                        if (!opt.rawmode)
                        {
                            opt.rawmode = true;
                            Console.Error.WriteLine("WARNING: Raw bits/sample specified for non-raw data. Assuming input is raw.");
                        }
                        try
                        {
                            opt.raw_samplesize = int.Parse(g.Optarg);
                        }
                        catch (Exception)
                        {
                            opt.raw_samplesize = 16; /* Failed, so just set to 16 */
                            Console.Error.WriteLine("WARNING: Invalid bits/sample specified, assuming 16.");
                        }
                        if ((opt.raw_samplesize != 8) && (opt.raw_samplesize != 16))
                        {
                            Console.Error.WriteLine("WARNING: Invalid bits/sample specified, assuming 16.");
                        }
                        break;
                    case 'C':
                        if (!opt.rawmode)
                        {
                            opt.rawmode = true;
                            Console.Error.WriteLine("WARNING: Raw channel count specified for non-raw data. Assuming input is raw.");
                        }
                        try
                        {
                            opt.raw_channels = int.Parse(g.Optarg);
                        }
                        catch (Exception)
                        {
                            opt.raw_channels = 2; /* Failed, so just set to 2 */
                            Console.Error.WriteLine("WARNING: Invalid channel count specified, assuming 2.");
                        }
                        break;
                    case 'N':
                        opt.tracknum = new string[(++opt.track_count)];
                        opt.tracknum[opt.track_count - 1] = string.Copy(g.Optarg);
                        break;
                    case 'R':
                        if (!opt.rawmode)
                        {
                            opt.rawmode = true;
                            Console.Error.WriteLine("WARNING: Raw sample rate specified for non-raw data. Assuming input is raw.");
                        }
                        try
                        {
                            opt.raw_samplerate = int.Parse(g.Optarg);
                        }
                        catch (Exception)
                        {
                            opt.raw_samplerate = 44100; /* Failed, so just set to 44100 */
                            Console.Error.WriteLine("WARNING: Invalid sample rate specified, assuming 44100.");
                        }
                        break;
                    case '?':
                        Console.Error.WriteLine("WARNING: Unknown option specified, ignoring->");
                        break;
                    default:
                        usage();
                        Environment.Exit(0);
                        break;
                }
            }
        }

        static void add_tag(VorbisComment vc, oe_options opt, string name, string val)
        {
            Encoder e = Encoding.UTF8.GetEncoder();
            byte[] utf8 = new byte[e.GetByteCount(val.ToCharArray(), 0, val.Length, true)];
            if (e.GetBytes(val.ToCharArray(), 0, val.Length, utf8, 0, true) >= 0)
            {
                if (name == null)
                    VorbisComment.Add(vc, utf8);
                else
                    VorbisComment.AddTag(vc, name, utf8);
                utf8 = null;
            }
            else
                Console.Error.WriteLine("Couldn't convert comment to UTF-8, cannot add");
        }

        private static void build_comments(VorbisComment vc, oe_options opt, int filenum,
            ref string artist, ref string album, ref string title, ref string tracknum,
            ref string date, ref string genre)
        {
            int i;

            VorbisComment.Init(vc);

            for (i = 0; i < opt.comment_count; i++)
                add_tag(vc, opt, null, opt.comments[i]);

            if (opt.title_count != 0)
            {
                if (filenum >= opt.title_count)
                {
                    if (!opt.quiet)
                        Console.Error.WriteLine("WARNING: Insufficient titles specified, defaulting to final title.");
                    i = opt.title_count - 1;
                }
                else
                    i = filenum;

                title = opt.title[i];
                add_tag(vc, opt, "title", opt.title[i]);
            }

            if (opt.artist_count != 0)
            {
                if (filenum >= opt.artist_count)
                    i = opt.artist_count - 1;
                else
                    i = filenum;

                artist = opt.artist[i];
                add_tag(vc, opt, "artist", opt.artist[i]);
            }

            if (opt.genre_count != 0)
            {
                if (filenum >= opt.genre_count)
                    i = opt.genre_count - 1;
                else
                    i = filenum;

                genre = opt.genre[i];
                add_tag(vc, opt, "genre", opt.genre[i]);
            }

            if (opt.date_count != 0)
            {
                if (filenum >= opt.date_count)
                    i = opt.date_count - 1;
                else
                    i = filenum;

                date = opt.dates[i];
                add_tag(vc, opt, "date", opt.dates[i]);
            }

            if (opt.album_count != 0)
            {
                if (filenum >= opt.album_count)
                {
                    i = opt.album_count - 1;
                }
                else
                    i = filenum;

                album = opt.album[i];
                add_tag(vc, opt, "album", opt.album[i]);
            }

            if (filenum < opt.track_count)
            {
                i = filenum;
                tracknum = opt.tracknum[i];
                add_tag(vc, opt, "tracknumber", opt.tracknum[i]);
            }
        }
    }
}